/*
 *	Wireless Tools
 *
 *		Jean II - HPL 99->04
 *
 * Main code for "iwevent". This listent for wireless events on rtnetlink.
 * You need to link this code against "iwcommon.c" and "-lm".
 *
 * Part of this code is from Alexey Kuznetsov, part is from Casey Carter,
 * I've just put the pieces together...
 * By the way, if you know a way to remove the root restrictions, tell me
 * about it...
 *
 * This file is released under the GPL license.
 *     Copyright (c) 1997-2004 Jean Tourrilhes <jt@hpl.hp.com>
 */

/***************************** INCLUDES *****************************/

#include "iwlib.h"		/* Header */

#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <getopt.h>
#include <time.h>
#include <sys/time.h>

/* Ugly backward compatibility :-( */
#ifndef IFLA_WIRELESS
#define IFLA_WIRELESS	(IFLA_MASTER + 1)
#endif /* IFLA_WIRELESS */

/****************************** TYPES ******************************/

/*
 * Static information about wireless interface.
 * We cache this info for performance reason.
 */
typedef struct wireless_iface
{
    /* Linked list */
    struct wireless_iface *	next;

    /* Interface identification */
    int		ifindex;		/* Interface index == black magic */

    /* Interface data */
    char			ifname[IFNAMSIZ + 1];	/* Interface name */
    struct iw_range	range;			/* Wireless static data */
    int			has_range;
} wireless_iface;

/**************************** VARIABLES ****************************/

/* Cache of wireless interfaces */
struct wireless_iface *	interface_cache = NULL;

/************************ RTNETLINK HELPERS ************************/
/*
 * The following code is extracted from :
 * ----------------------------------------------
 * libnetlink.c	RTnetlink service routines.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
 * -----------------------------------------------
 */

struct rtnl_handle
{
    int			fd;
    struct sockaddr_nl	local;
    struct sockaddr_nl	peer;
    __u32			seq;
    __u32			dump;
};

static inline void rtnl_close(struct rtnl_handle *rth)
{
    close(rth->fd);
}

static inline int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
{
    int addr_len;

    memset(rth, 0, sizeof(rth));

    rth->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (rth->fd < 0) {
        perror("Cannot open netlink socket");
        return -1;
    }

    memset(&rth->local, 0, sizeof(rth->local));
    rth->local.nl_family = AF_NETLINK;
    rth->local.nl_groups = subscriptions;

    if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) {
        perror("Cannot bind netlink socket");
        return -1;
    }
    addr_len = sizeof(rth->local);
    if (getsockname(rth->fd, (struct sockaddr*)&rth->local,
                (socklen_t *) &addr_len) < 0) {
        perror("Cannot getsockname");
        return -1;
    }
    if (addr_len != sizeof(rth->local)) {
        fprintf(stderr, "Wrong address length %d\n", addr_len);
        return -1;
    }
    if (rth->local.nl_family != AF_NETLINK) {
        fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family);
        return -1;
    }
    rth->seq = time(NULL);
    return 0;
}

/******************* WIRELESS INTERFACE DATABASE *******************/
/*
 * We keep a few information about each wireless interface on the
 * system. This avoid to query this info at each event, therefore
 * reducing overhead.
 *
 * Each interface is indexed by the 'ifindex'. As opposed to interface
 * names, 'ifindex' are never reused (even if you reactivate the same
 * hardware), so the data we cache will never apply to the wrong
 * interface.
 * Because of that, we are pretty lazy when it come to purging the
 * cache...
 */

/*------------------------------------------------------------------*/
/*
 * Get name of interface based on interface index...
 */
    static inline int
index2name(int		skfd,
        int		ifindex,
        char *	name)
{
    struct ifreq	irq;
    int		ret = 0;

    memset(name, 0, IFNAMSIZ + 1);

    /* Get interface name */
    irq.ifr_ifindex = ifindex;
    if(ioctl(skfd, SIOCGIFNAME, &irq) < 0)
        ret = -1;
    else
        strncpy(name, irq.ifr_name, IFNAMSIZ);

    return(ret);
}

/*------------------------------------------------------------------*/
/*
 * Get interface data from cache or live interface
 */
    static struct wireless_iface *
iw_get_interface_data(int	ifindex)
{
    struct wireless_iface *	curr;
    int				skfd = -1;	/* ioctl socket */

    /* Search for it in the database */
    curr = interface_cache;
    while(curr != NULL)
    {
        /* Match ? */
        if(curr->ifindex == ifindex)
        {
            //printf("Cache : found %d-%s\n", curr->ifindex, curr->ifname);

            /* Return */
            return(curr);
        }
        /* Next entry */
        curr = curr->next;
    }

    /* Create a channel to the NET kernel. Doesn't happen too often, so
     * socket creation overhead is minimal... */
    if((skfd = iw_sockets_open()) < 0)
    {
        perror("iw_sockets_open");
        return(NULL);
    }

    /* Create new entry, zero, init */
    curr = calloc(1, sizeof(struct wireless_iface));
    if(!curr)
    {
        fprintf(stderr, "Malloc failed\n");
        return(NULL);
    }
    curr->ifindex = ifindex;

    /* Extract static data */
    if(index2name(skfd, ifindex, curr->ifname) < 0)
    {
        perror("index2name");
        free(curr);
        return(NULL);
    }
    curr->has_range = (iw_get_range_info(skfd, curr->ifname, &curr->range) >= 0);
    //printf("Cache : create %d-%s\n", curr->ifindex, curr->ifname);

    /* Done */
    iw_sockets_close(skfd);

    /* Link it */
    curr->next = interface_cache;
    interface_cache = curr;

    return(curr);
}

/*------------------------------------------------------------------*/
/*
 * Remove interface data from cache (if it exist)
 */
    static void
iw_del_interface_data(int	ifindex)
{
    struct wireless_iface *	curr;
    struct wireless_iface *	prev = NULL;
    struct wireless_iface *	next;

    /* Go through the list, find the interface, kills it */
    curr = interface_cache;
    while(curr)
    {
        next = curr->next;

        /* Got a match ? */
        if(curr->ifindex == ifindex)
        {
            /* Unlink. Root ? */
            if(!prev)
                interface_cache = next;
            else
                prev->next = next;
            //printf("Cache : purge %d-%s\n", curr->ifindex, curr->ifname);

            /* Destroy */
            free(curr);
        }
        else
        {
            /* Keep as previous */
            prev = curr;
        }

        /* Next entry */
        curr = next;
    }
}

/********************* WIRELESS EVENT DECODING *********************/
/*
 * Parse the Wireless Event and print it out
 */

/*------------------------------------------------------------------*/
/*
 * Dump a buffer as a serie of hex
 * Maybe should go in iwlib...
 * Maybe we should have better formatting like iw_print_key...
 */
    static char *
iw_hexdump(char *		buf,
        size_t		buflen,
        const unsigned char *data,
        size_t		datalen)
{
    size_t	i;
    char *	pos = buf;

    for(i = 0; i < datalen; i++)
        pos += snprintf(pos, buf + buflen - pos, "%02X", data[i]);
    return buf;
}

/*------------------------------------------------------------------*/
/*
 * Print one element from the scanning results
 */
    static inline int
print_event_token(struct iw_event *	event,		/* Extracted token */
        struct iw_range *	iw_range,	/* Range info */
        int			has_range)
{
    char		buffer[128];	/* Temporary buffer */
    char		buffer2[30];	/* Temporary buffer */
    char *	prefix = (IW_IS_GET(event->cmd) ? "New" : "Set");

    /* Now, let's decode the event */
    switch(event->cmd)
    {
        /* ----- set events ----- */
        /* Events that result from a "SET XXX" operation by the user */
        case SIOCSIWNWID:
            if(event->u.nwid.disabled)
                printf("Set NWID:off/any\n");
            else
                printf("Set NWID:%X\n", event->u.nwid.value);
            break;
        case SIOCSIWFREQ:
        case SIOCGIWFREQ:
            {
                double		freq;			/* Frequency/channel */
                int		channel = -1;		/* Converted to channel */
                freq = iw_freq2float(&(event->u.freq));
                if(has_range)
                {
                    if(freq < KILO)
                        /* Convert channel to frequency if possible */
                        channel = iw_channel_to_freq((int) freq, &freq, iw_range);
                    else
                        /* Convert frequency to channel if possible */
                        channel = iw_freq_to_channel(freq, iw_range);
                }
                iw_print_freq(buffer, sizeof(buffer),
                        freq, channel, event->u.freq.flags);
                printf("%s %s\n", prefix, buffer);
            }
            break;
        case SIOCSIWMODE:
            printf("Set Mode:%s\n",
                    iw_operation_mode[event->u.mode]);
            break;
        case SIOCSIWESSID:
        case SIOCGIWESSID:
            {
                char essid[IW_ESSID_MAX_SIZE+1];
                memset(essid, '\0', sizeof(essid));
                if((event->u.essid.pointer) && (event->u.essid.length))
                    memcpy(essid, event->u.essid.pointer, event->u.essid.length);
                if(event->u.essid.flags)
                {
                    /* Does it have an ESSID index ? */
                    if((event->u.essid.flags & IW_ENCODE_INDEX) > 1)
                        printf("%s ESSID:\"%s\" [%d]\n", prefix, essid,
                                (event->u.essid.flags & IW_ENCODE_INDEX));
                    else
                        printf("%s ESSID:\"%s\"\n", prefix, essid);
                }
                else
                    printf("%s ESSID:off/any\n", prefix);
            }
            break;
        case SIOCSIWENCODE:
            {
                unsigned char	key[IW_ENCODING_TOKEN_MAX];
                if(event->u.data.pointer)
                    memcpy(key, event->u.data.pointer, event->u.data.length);
                else
                    event->u.data.flags |= IW_ENCODE_NOKEY;
                printf("Set Encryption key:");
                if(event->u.data.flags & IW_ENCODE_DISABLED)
                    printf("off\n");
                else
                {
                    /* Display the key */
                    iw_print_key(buffer, sizeof(buffer), key, event->u.data.length,
                            event->u.data.flags);
                    printf("%s", buffer);

                    /* Other info... */
                    if((event->u.data.flags & IW_ENCODE_INDEX) > 1)
                        printf(" [%d]", event->u.data.flags & IW_ENCODE_INDEX);
                    if(event->u.data.flags & IW_ENCODE_RESTRICTED)
                        printf("   Security mode:restricted");
                    if(event->u.data.flags & IW_ENCODE_OPEN)
                        printf("   Security mode:open");
                    printf("\n");
                }
            }
            break;
            /* ----- driver events ----- */
            /* Events generated by the driver when something important happens */
        case SIOCGIWAP:
            printf("New Access Point/Cell address:%s\n",
                    iw_sawap_ntop(&event->u.ap_addr, buffer));
            break;
        case SIOCGIWSCAN:
            printf("Scan request completed\n");
            break;
        case IWEVTXDROP:
            printf("Tx packet dropped:%s\n",
                    iw_saether_ntop(&event->u.addr, buffer));
            break;
        case IWEVCUSTOM:
            {
                char custom[IW_CUSTOM_MAX+1];
                memset(custom, '\0', sizeof(custom));
                if((event->u.data.pointer) && (event->u.data.length))
                    memcpy(custom, event->u.data.pointer, event->u.data.length);
                printf("Custom driver event:%s\n", custom);
            }
            break;
        case IWEVREGISTERED:
            printf("Registered node:%s\n",
                    iw_saether_ntop(&event->u.addr, buffer));
            break;
        case IWEVEXPIRED:
            printf("Expired node:%s\n",
                    iw_saether_ntop(&event->u.addr, buffer));
            break;
        case SIOCGIWTHRSPY:
            {
                struct iw_thrspy	threshold;
                if((event->u.data.pointer) && (event->u.data.length))
                {
                    memcpy(&threshold, event->u.data.pointer,
                            sizeof(struct iw_thrspy));
                    printf("Spy threshold crossed on address:%s\n",
                            iw_saether_ntop(&threshold.addr, buffer));
                    iw_print_stats(buffer, sizeof(buffer),
                            &threshold.qual, iw_range, has_range);
                    printf("                            Link %s\n", buffer);
                }
                else
                    printf("Invalid Spy Threshold event\n");
            }
            break;
            /* ----- driver WPA events ----- */
            /* Events generated by the driver, used for WPA operation */
        case IWEVMICHAELMICFAILURE:
            if(event->u.data.length >= sizeof(struct iw_michaelmicfailure))
            {
                struct iw_michaelmicfailure mf;
                memcpy(&mf, event->u.data.pointer, sizeof(mf));
                printf("Michael MIC failure flags:0x%X src_addr:%s tsc:%s\n",
                        mf.flags,
                        iw_saether_ntop(&mf.src_addr, buffer2),
                        iw_hexdump(buffer, sizeof(buffer),
                            mf.tsc, IW_ENCODE_SEQ_MAX_SIZE));
            }
            break;
        case IWEVASSOCREQIE:
            printf("Association Request IEs:%s\n",
                    iw_hexdump(buffer, sizeof(buffer),
                        event->u.data.pointer, event->u.data.length));
            break;
        case IWEVASSOCRESPIE:
            printf("Association Response IEs:%s\n",
                    iw_hexdump(buffer, sizeof(buffer),
                        event->u.data.pointer, event->u.data.length));
            break;
        case IWEVPMKIDCAND:
            if(event->u.data.length >= sizeof(struct iw_pmkid_cand))
            {
                struct iw_pmkid_cand cand;
                memcpy(&cand, event->u.data.pointer, sizeof(cand));
                printf("PMKID candidate flags:0x%X index:%d bssid:%s\n",
                        cand.flags, cand.index,
                        iw_saether_ntop(&cand.bssid, buffer));
            }
            break;
            /* ----- junk ----- */
            /* other junk not currently in use */
        case SIOCGIWRATE:
            iw_print_bitrate(buffer, sizeof(buffer), event->u.bitrate.value);
            printf("New Bit Rate:%s\n", buffer);
            break;
        case SIOCGIWNAME:
            printf("Protocol:%-1.16s\n", event->u.name);
            break;
        case IWEVQUAL:
            {
                event->u.qual.updated = 0x0;	/* Not that reliable, disable */
                iw_print_stats(buffer, sizeof(buffer),
                        &event->u.qual, iw_range, has_range);
                printf("Link %s\n", buffer);
                break;
            }
        default:
            printf("(Unknown Wireless event 0x%04X)\n", event->cmd);
    }	/* switch(event->cmd) */

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Print out all Wireless Events part of the RTNetlink message
 * Most often, there will be only one event per message, but
 * just make sure we read everything...
 */
    static inline int
print_event_stream(int		ifindex,
        char *	data,
        int		len)
{
    struct iw_event	iwe;
    struct stream_descr	stream;
    int			i = 0;
    int			ret;
    char			buffer[64];
    struct timeval	recv_time;
    struct timezone	tz;
    struct wireless_iface *	wireless_data;

    /* Get data from cache */
    wireless_data = iw_get_interface_data(ifindex);
    if(wireless_data == NULL)
        return(-1);

    /* Print received time in readable form */
    gettimeofday(&recv_time, &tz);
    iw_print_timeval(buffer, sizeof(buffer), &recv_time, &tz);

    iw_init_event_stream(&stream, data, len);
    do
    {
        /* Extract an event and print it */
        ret = iw_extract_event_stream(&stream, &iwe,
                wireless_data->range.we_version_compiled);
        if(ret != 0)
        {
            if(i++ == 0)
                printf("%s   %-8.16s ", buffer, wireless_data->ifname);
            else
                printf("                           ");
            if(ret > 0)
                print_event_token(&iwe,
                        &wireless_data->range, wireless_data->has_range);
            else
                printf("(Invalid event)\n");
            /* Push data out *now*, in case we are redirected to a pipe */
            fflush(stdout);
        }
    }
    while(ret > 0);

    return(0);
}

/*********************** RTNETLINK EVENT DUMP***********************/
/*
 * Dump the events we receive from rtnetlink
 * This code is mostly from Casey
 */

/*------------------------------------------------------------------*/
/*
 * Respond to a single RTM_NEWLINK event from the rtnetlink socket.
 */
    static int
LinkCatcher(struct nlmsghdr *nlh)
{
    struct ifinfomsg* ifi;

#if 0
    fprintf(stderr, "nlmsg_type = %d.\n", nlh->nlmsg_type);
#endif

    ifi = NLMSG_DATA(nlh);

    /* Code is ugly, but sort of works - Jean II */

    /* If interface is getting destoyed */
    if(nlh->nlmsg_type == RTM_DELLINK)
    {
        /* Remove from cache (if in cache) */
        iw_del_interface_data(ifi->ifi_index);
        return 0;
    }

    /* Only keep add/change events */
    if(nlh->nlmsg_type != RTM_NEWLINK)
        return 0;

    /* Check for attributes */
    if (nlh->nlmsg_len > NLMSG_ALIGN(sizeof(struct ifinfomsg)))
    {
        int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct ifinfomsg));
        struct rtattr *attr = (void *) ((char *) ifi +
                NLMSG_ALIGN(sizeof(struct ifinfomsg)));

        while (RTA_OK(attr, attrlen))
        {
            /* Check if the Wireless kind */
            if(attr->rta_type == IFLA_WIRELESS)
            {
                /* Go to display it */
                print_event_stream(ifi->ifi_index,
                        (char *) attr + RTA_ALIGN(sizeof(struct rtattr)),
                        attr->rta_len - RTA_ALIGN(sizeof(struct rtattr)));
            }
            attr = RTA_NEXT(attr, attrlen);
        }
    }

    return 0;
}

/* ---------------------------------------------------------------- */
/*
 * We must watch the rtnelink socket for events.
 * This routine handles those events (i.e., call this when rth.fd
 * is ready to read).
 */
    static inline void
handle_netlink_events(struct rtnl_handle *	rth)
{
    while(1)
    {
        struct sockaddr_nl sanl;
        socklen_t sanllen = sizeof(struct sockaddr_nl);

        struct nlmsghdr *h;
        int amt;
        char buf[8192];

        amt = recvfrom(rth->fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&sanl, &sanllen);
        if(amt < 0)
        {
            if(errno != EINTR && errno != EAGAIN)
            {
                fprintf(stderr, "%s: error reading netlink: %s.\n",
                        __PRETTY_FUNCTION__, strerror(errno));
            }
            return;
        }

        if(amt == 0)
        {
            fprintf(stderr, "%s: EOF on netlink??\n", __PRETTY_FUNCTION__);
            return;
        }

        h = (struct nlmsghdr*)buf;
        while(amt >= (int)sizeof(*h))
        {
            int len = h->nlmsg_len;
            int l = len - sizeof(*h);

            if(l < 0 || len > amt)
            {
                fprintf(stderr, "%s: malformed netlink message: len=%d\n", __PRETTY_FUNCTION__, len);
                break;
            }

            switch(h->nlmsg_type)
            {
                case RTM_NEWLINK:
                case RTM_DELLINK:
                    LinkCatcher(h);
                    break;
                default:
#if 0
                    fprintf(stderr, "%s: got nlmsg of type %#x.\n", __PRETTY_FUNCTION__, h->nlmsg_type);
#endif
                    break;
            }

            len = NLMSG_ALIGN(len);
            amt -= len;
            h = (struct nlmsghdr*)((char*)h + len);
        }

        if(amt > 0)
            fprintf(stderr, "%s: remnant of size %d on netlink\n", __PRETTY_FUNCTION__, amt);
    }
}

/**************************** MAIN LOOP ****************************/

/* ---------------------------------------------------------------- */
/*
 * Wait until we get an event
 */
    static inline int
wait_for_event(struct rtnl_handle *	rth)
{
#if 0
    struct timeval	tv;	/* Select timeout */
#endif

    /* Forever */
    while(1)
    {
        fd_set		rfds;		/* File descriptors for select */
        int		last_fd;	/* Last fd */
        int		ret;

        /* Guess what ? We must re-generate rfds each time */
        FD_ZERO(&rfds);
        FD_SET(rth->fd, &rfds);
        last_fd = rth->fd;

        /* Wait until something happens */
        ret = select(last_fd + 1, &rfds, NULL, NULL, NULL);

        /* Check if there was an error */
        if(ret < 0)
        {
            if(errno == EAGAIN || errno == EINTR)
                continue;
            fprintf(stderr, "Unhandled signal - exiting...\n");
            break;
        }

        /* Check if there was a timeout */
        if(ret == 0)
        {
            continue;
        }

        /* Check for interface discovery events. */
        if(FD_ISSET(rth->fd, &rfds))
            handle_netlink_events(rth);
    }

    return(0);
}

/******************************* MAIN *******************************/

/* ---------------------------------------------------------------- */
/*
 * helper ;-)
 */
    static void
iw_usage(int status)
{
    fputs("Usage: iwevent [OPTIONS]\n"
            "   Monitors and displays Wireless Events.\n"
            "   Options are:\n"
            "     -h,--help     Print this message.\n"
            "     -v,--version  Show version of this program.\n",
            status ? stderr : stdout);
    exit(status);
}
/* Command line options */
static const struct option long_opts[] = {
    { "help", no_argument, NULL, 'h' },
    { "version", no_argument, NULL, 'v' },
    { NULL, 0, NULL, 0 }
};

/* ---------------------------------------------------------------- */
/*
 * main body of the program
 */
    int
main(int	argc,
        char *	argv[])
{
    struct rtnl_handle	rth;
    int opt;

    /* Check command line options */
    while((opt = getopt_long(argc, argv, "hv", long_opts, NULL)) > 0)
    {
        switch(opt)
        {
            case 'h':
                iw_usage(0);
                break;

            case 'v':
                return(iw_print_version_info("iwevent"));
                break;

            default:
                iw_usage(1);
                break;
        }
    }
    if(optind < argc)
    {
        fputs("Too many arguments.\n", stderr);
        iw_usage(1);
    }

    /* Open netlink channel */
    if(rtnl_open(&rth, RTMGRP_LINK) < 0)
    {
        perror("Can't initialize rtnetlink socket");
        return(1);
    }

    fprintf(stderr, "Waiting for Wireless Events from interfaces...\n");

    /* Do what we have to do */
    wait_for_event(&rth);

    /* Cleanup - only if you are pedantic */
    rtnl_close(&rth);

    return(0);
}
